Repository navigation
fix: handle relative schema references without fragment and prevent UriFormatException (#3148) - #3153
Conversation
…icrosoft#3148) Fix GetReferenceIdAndExternalResource in OpenApiV31Deserializer and OpenApiV32Deserializer so that references to external documents without a fragment (e.g., schemas/uuid.schema.json or an absolute URL) correctly populate ExternalResource and mark IsExternal = true. Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
…icrosoft#3148) Update OpenApiDocument.ResolveReference and OpenApiWorkspace.ToLocationUrl to resolve relative references safely against BaseUri or external document URIs using Uri.TryCreate, preventing unhandled UriFormatException and ArgumentNullException when resolving relative references without fragments. Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
There was a problem hiding this comment.
🟡 Changes recommended
The changes break external-component resolution and valid discriminator mappings, alter fragmentless relative references during serialization, and misinterpret root-relative workspace URLs as file URIs.
4 open issues
Preserve fragments when external resource and component names collide · New Preserve fragmentless relative $ref pointers during round trips · New Keep local discriminator mappings from being treated as external resources · New Resolve root-relative locations against BaseUrl before absolute parsing · New
What changed in this PR
This PR addresses #3148 by recognizing fragmentless external schema references in the OpenAPI 3.1 and 3.2 readers and making URI resolution safer in the document and workspace layers.
Changes:
- Classifies fragmentless schema references as external resources.
- Uses safe URI construction for reference resolution and supports absolute workspace locations without a base URL.
- Adds regression tests for deserialization, parsing, and workspace-backed resolution.
| File | Description |
|---|---|
test/Microsoft.OpenApi.Tests/Workspaces/OpenApiWorkspaceTests.cs |
Tests absolute URI lookup without a configured base URL. |
test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs |
Tests fragmentless relative references with and without registered targets. |
test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaReferenceDeserializerTests.cs |
Tests fragmentless external references in OpenAPI 3.2. |
test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs |
Adds a parsing regression test for a relative schema reference. |
test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaReferenceDeserializerTests.cs |
Tests fragmentless external references in OpenAPI 3.1. |
src/Microsoft.OpenApi/Services/OpenApiWorkspace.cs |
Updates absolute and relative URI construction for workspace lookup. |
src/Microsoft.OpenApi/Reader/V32/OpenApiV32Deserializer.cs |
Recognizes external resources without fragments. |
src/Microsoft.OpenApi/Reader/V31/OpenApiV31Deserializer.cs |
Recognizes external resources without fragments. |
src/Microsoft.OpenApi/Models/OpenApiDocument.cs |
Handles standalone external references and safely constructs resolution URIs. |
Review effort: Balanced
| string uriLocation; | ||
| var id = reference.Id; | ||
| if (!string.IsNullOrEmpty(id) && id!.Contains('/')) // this means its a URL reference | ||
| if (!string.IsNullOrEmpty(id) && (id!.Contains('/') || (reference.IsExternal && reference.ExternalResource == id))) // this means its a URL or standalone external reference |
There was a problem hiding this comment.
Addressed in commit d0eeafa. In OpenApiDocument.ResolveReference, standalone external references are now identified by checking that the reference has no URI fragment (!referenceV3.Contains(#)), rather than testing ExternalResource == id. When external component references have colliding names (e.g., common.json#/components/schemas/common.json), the component fragment is preserved and resolves the component inside the external document instead of looking up the document URI. Added regression test ResolveReference_ExternalComponentCollidingWithDocumentNameResolvesComponent.
| var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase); | ||
| string? externalResource = null; | ||
| if (isExternalResource && pointer.Contains('#')) | ||
| if (isExternalResource) |
There was a problem hiding this comment.
Addressed in commit d0eeafa. In BaseOpenApiReference.SetJsonPointerPath, authored pointers are now preserved directly without requiring # or http, correctly maintaining fragmentless relative references such as schemas/uuid.schema.json. In GetExternalReferenceV3(), standalone external references where ExternalResource == Id && !Id.Contains(#) return ExternalResource directly. Added round-trip serialization assertions in RelativeReferenceTests.cs and OpenApiSchemaReferenceDeserializerTests.cs.
| var isExternalResource = !refSegments[0].StartsWith("#", StringComparison.OrdinalIgnoreCase); | ||
| string? externalResource = null; | ||
| if (isExternalResource && pointer.Contains('#')) | ||
| if (isExternalResource) |
There was a problem hiding this comment.
Addressed in commit d0eeafa. In both OpenApiDiscriminatorDeserializer (V31 and V32), LoadMapping now detects bare schema names (via IsBareSchemaName, checking that the value is not a URI reference or file path) and constructs a local OpenApiSchemaReference(pointer, hostDocument) with ExternalResource = null. This resolves the local schema in hostDocument.Components.Schemas properly for both mapping and defaultMapping (and x-oas-default-mapping). Added regression tests in V31Tests/OpenApiDiscriminatorTests.cs and V32Tests/OpenApiDiscriminatorTests.cs.
| if (Uri.TryCreate(location, UriKind.Absolute, out var absoluteUri)) | ||
| { | ||
| return new(BaseUrl, location); | ||
| return absoluteUri; |
There was a problem hiding this comment.
Addressed in commit d0eeafa. In OpenApiWorkspace.ToLocationUrl, we now combine with BaseUrl first via Uri.TryCreate(BaseUrl, location, out var relativeUri) before falling back to standalone absolute-URI parsing. This preserves origin resolution for root-relative locations such as /schemas/common.json against an HTTPS BaseUrl without misinterpreting them as file URIs, while retaining support for absolute URIs when no BaseUrl is configured. Added regression test OpenApiWorkspacesResolvesRootRelativeUriAgainstBaseUrl.
… mappings, and workspace URLs (microsoft#3148) - Preserve component fragments in OpenApiDocument.ResolveReference when external resource and component names collide (e.g. common.json#/components/schemas/common.json). - Preserve authored fragmentless relative pointers in BaseOpenApiReference.SetJsonPointerPath and GetExternalReferenceV3 to maintain exact round trips during serialization. - Preserve bare schema names in OpenApiDiscriminatorDeserializer.LoadMapping so local schemas in discriminator mapping and defaultMapping resolve properly. - Resolve root-relative locations against BaseUrl in OpenApiWorkspace.ToLocationUrl before fallback to absolute URI parsing. - Catch loader errors gracefully in OpenApiWorkspaceLoader to report diagnostics without aborting document resolution. - Add regression unit tests for colliding external references, discriminator bare schema mappings, workspace root-relative URI resolution, and round-trip serialization. Signed-off-by: Hardik Kaurani <hardikkaurani1@gmail.com>
|
I've pushed an update addressing all Copilot review findings in commit [d0eeafad](d0eeafa). Key updates:
Added regression tests covering all four scenarios. Thank you for the detailed feedback. Whenever convenient, please take another look! |

Resolves #3148
Summary of Changes
External Resource Deserialization (OpenAPI 3.1 & 3.2):
OpenApiV31Deserializer.GetReferenceIdAndExternalResourceandOpenApiV32Deserializer.GetReferenceIdAndExternalResource, removed the&& pointer.Contains('#')constraint when determiningexternalResource.schemas/uuid.schema.json,./schemas/uuid.schema.json, or absolute schema URLs) now properly setExternalResourceand evaluateIsExternal = true, aligning behavior withOpenApiV3Deserializerand the OpenAPI 3.1/3.2 specification.Safe Relative URI Resolution:
OpenApiDocument.ResolveReference, standalone external references (reference.IsExternal && reference.ExternalResource == id) are routed directly touriLocation = id, avoiding an unintended#/components/schemas/{id}relative fragment suffix.new Uri(uriLocation)with safe URI construction usingUri.TryCreate. Relative references now combine safely againstBaseUrior the registered external document URI in the workspace without throwing unhandledUriFormatExceptionorArgumentNullException. When no base URI is available, the resolution returnsnullgracefully.OpenApiWorkspace.ToLocationUrl, added direct support for resolving absolute URI locations even whenBaseUrlis not configured, while safely falling back toBaseUrlfor relative locations viaUri.TryCreate.Regression Tests:
OpenApiSchemaReferenceDeserializerTests(V31 and V32) covering external reference pointers without fragments (schemas/uuid.schema.json,./schemas/uuid.schema.json,uuid.schema.json, and absolute URLs).RelativeReferenceTests(V31) verifying that documents containing relative schema references without fragments parse and pass validation without throwingUriFormatException.OpenApiDocumentTestsverifying safe reference resolution with and without workspace targets.OpenApiWorkspaceTestsverifying resolution of absolute URI references.Commits
fix(v31): recognize external resource in schema ref without fragment (#3148)fix(models): resolve relative reference URIs safely against BaseUri (#3148)